import std.stdio: writefln;
import std.string: toStringz;
import derelict.sdl.sdl;

import std.math;

import Point;
import Pixels;
import Lines;
import Circles;

import BackPropagationNetwork;

const int   xResolution     = 800;
const int   yResolution     = 600;
const int   bitsPerPixel    = 32;

uint background;

const float l1 = 150;
const float l2 = 100;

int mouseX, mouseY;

SDL_Surface *screen;
uint buf[];
BackPropagationNetwork bpn;

void init() {
    DerelictSDL.load();

    // initialize SDL's VIDEO module
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
    screen = SDL_SetVideoMode(xResolution, yResolution, bitsPerPixel, SDL_SWSURFACE );
    SDL_WM_SetCaption(toStringz("zad4"), null);
	setBackground(screen, 0x000000);
}

void setBackground(SDL_Surface *surface, uint color){
	uint *pos = cast(uint*)(surface.pixels);
	background = color;
	pos[0..xResolution*yResolution] = color;
	SDL_UpdateRect(screen,0,0,0,0);
}


// be nice and release all resources
void cleanup() {
    SDL_Quit();
    DerelictSDL.unload();
}

void keyDown(SDL_KeyboardEvent e){
    if(e.keysym.sym == SDLK_ESCAPE) throw new Exception("KungFuDeathGrip");
/+	if(e.keysym.sym == SDLK_RIGHT) images.getNext(imageIn);
	if(e.keysym.sym == SDLK_LEFT) images.getPrevious(imageIn);
	if(e.keysym.sym == SDLK_a) images.addFromBox(imageIn);
	if(e.keysym.sym == SDLK_x) perceptrons.detect(imageIn, imageOut);+/
}



void drawRobot(float psiN, float chiN){
	int bX = cast(int)xResolution/2;
	int bY = cast(int)yResolution/2;
	float psi = 2 * cast(float)M_PI * psiN;
	int eX = cast(int)(l1*sin(psi));
	int eY = cast(int)(l1*cos(psi));

	line(screen, point(bX, bY), point(eX+bX, bY+eY), 0xFF0000);

	float chi = cast(float)M_PI * chiN;
	float eX1f = (l2*sin(chi));
	float eY1f = (l2*cos(chi));
	int eX1 = cast(int)(eX1f*cos(psi) + eY1f*sin(psi));
	int eY1 = cast(int)(-eX1f*sin(psi) + eY1f*cos(psi));

	line(screen, point(eX+bX, bY+eY), point(eX+bX + eX1, bY+eY + eY1), 0xFF0000);
}

void drawFrame(){
	circle(screen, cast(int)xResolution/2, cast(int)yResolution/2, cast(int)(l1-l2), 1234);
//	circle(screen, cast(int)xResolution/2, cast(int)yResolution/2, cast(int)l1, 1234);
	circle(screen, cast(int)xResolution/2, cast(int)yResolution/2, cast(int)(l1+l2), 3234);
	line(screen, point(400, 300), point(mouseX, mouseY), 2345);
	
	if(dist2(mouseX, mouseY, 400, 300) < 250 * 250){
		float chi, psi;
		bpn.eval(cast(float)(mouseX-400+250)/500.0, cast(float)(mouseY-300+250)/500.0, psi, chi);
//		writefln(distFromSolve(mouseX, mouseY));
		drawRobot(psi, chi);
	}
}

float distFromSolve(int x, int y){
	float chiN, psiN;
	bpn.eval(cast(float)(x-400+250)/500.0, cast(float)(y-300+250)/500.0,psiN, chiN);

	int bX = cast(int)xResolution/2;
	int bY = cast(int)yResolution/2;
	float psi = 2 * cast(float)M_PI * psiN;
	float eX = (l1*sin(psi));
	float eY = (l1*cos(psi));

	float chi = cast(float)M_PI * chiN;
	float eX1f = (l2*sin(chi));
	float eY1f = (l2*cos(chi));
	float eX1 = (eX1f*cos(psi) + eY1f*sin(psi));
	float eY1 = (-eX1f*sin(psi) + eY1f*cos(psi));

	float x1 = eX+bX + eX1;
	float y1 = bY+eY + eY1;

	return sqrt(dist2(x,y,x1,y1));
}

float dist2(float x, float y, float x1, float y1){
	return (x-x1)*(x-x1)+(y-y1)*(y-y1);
}

void refresh(){
	SDL_UpdateRect(screen,0,0,0,0);
}

private void clearScreen(SDL_Surface *surface){
	uint *p = cast(uint *)surface.pixels;
	p[0..surface.w * surface.h] = buf;
}

void mouseButtonUp(SDL_MouseButtonEvent e){
//    imageIn.keyUp(e.button);
}

void mouseButtonDown(SDL_MouseButtonEvent e){
//    imageIn.keyDown(e.button);
}

void mouseMotion(SDL_MouseMotionEvent e){
/+    if ((e.x > (inputWindowX - 1) && e.x < (inputWindowX + imageIOWidth * scale )) &&
		(e.y > (inputWindowY - 1) && e.y < (inputWindowY + imageIOHeight* scale ))) {
        imageIn.mouseMotion(e.x - inputWindowX, e.y - inputWindowY);
    }+/
	mouseX = e.x;
	mouseY = e.y;
}

uint interp(uint x, uint y, float s){
//	writefln(s);
	assert(s<=1 && s>=0);
	uint r = cast(uint)( s*cast(float)(x % 0x100)  +  (s-1f)*cast(float)(y % 0x100) );
	x /= 0x100;
	y /= 0x100;
	uint g = cast(uint)( s*cast(float)(x % 0x100)  +  (s-1f)*cast(float)(y % 0x100) );
	x /= 0x100;
	y /= 0x100;
	uint b = cast(uint)( s*cast(float)(x)  +  (s-1f)*cast(float)(y) );
	return cast(uint)(0x010000 * r + 0x000100 * g + 0x000001 * b);
}

void main() {
	uint startTime;
	long frames;

	bpn = new BackPropagationNetwork(8, 16);
	writefln("Training network.");
	bpn.train(1000000, l1, l2);
	writefln("Done.");

	buf = new uint[xResolution * yResolution];

	for(int i = 0; i < xResolution;i++)
		for(int j = 0; j < yResolution;j++)
			if((dist2(i,j, 400, 300) < (l1+l2)*(l1+l2))){// && (dist2(i,j, 400, 300) > (l1-l2)*(l1-l2))){
				float dist = distFromSolve(i,j)/(2*(l1+l2));
				dist = sqrt(dist);
//				dist *= 255;
				assert (dist <= 1 && dist >=0);
//				writefln(dist);
				if(dist < 0.5f)
					buf[j*xResolution + i] = interp(0x00ff00, 0x0000ff, dist);
				else
					buf[j*xResolution + i] = interp(0x0000ff, 0xff0000, dist);
			}

    init();
//	writefln(SDL_MUSTLOCK(screen));
    scope(exit){
		writefln("\n Exiting.. \n %2.2f FPS\n",(cast(float)(frames)/(SDL_GetTicks()-startTime))*1000.f);
		cleanup();      // when we exit, perform cleanup
	}

	startTime = SDL_GetTicks();
    mainLoop:
    while (true) {
        SDL_Event event;

        // handle all SDL events that we might've received in this loop iteration
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                // user has clicked on the window's close button
                case SDL_QUIT:
                    break mainLoop;
				case SDL_KEYDOWN:
					keyDown(event.key);
					break;
				case SDL_MOUSEBUTTONDOWN:
					mouseButtonDown(event.button);
					break;
				case SDL_MOUSEBUTTONUP:
					mouseButtonUp(event.button);
					break;
				case SDL_MOUSEMOTION:
					mouseMotion(event.motion);
					break;
                // by default, we do nothing => break from the switch
                default:
                    break;
            }
        }
	
	SDL_LockSurface(screen);
	clearScreen(screen);
	drawFrame();
	SDL_UnlockSurface(screen);
	refresh();
//	SDL_UpdateRect(screen,0,0,0,0);
//	SDL_Flip(screen);
	frames++;
    }
}
